﻿#region Using

using System;
using System.Web;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Timers;
using System.Web.Security;
#endregion

/// <summary>
/// Block the response to attacking IP addresses.
/// </summary>
public class DosAttackModule : IHttpModule
{

    #region IHttpModule Members

    public void Dispose()
    {
        // Nothing to dispose; 
    }

    public void Init(HttpApplication context)
    {
        DosAttackModule.context = context;
        context.BeginRequest += new EventHandler(context_BeginRequest);
    }

    #endregion

    #region Private fields
    private static HttpApplication context;
    private static Dictionary<string, short> _IpAdresses = new Dictionary<string, short>();
    private static Stack<string> _Banned = new Stack<string>();
    private static Timer _Timer = CreateTimer();
    private static Timer _BannedTimer = CreateBanningTimer();

    #endregion

    private const int BANNED_REQUESTS = 50;//50;
    private const int REDUCTION_INTERVAL = 1000; // 1 second
    private const int RELEASE_INTERVAL = 5 * 60 * 1000; // 5 minutes

    public void context_BeginRequest(object sender, EventArgs e)
    {
        string ip = HttpContext.Current.Request.UserHostAddress;
        if (_Banned.Contains(ip))
        {
            HttpContext.Current.Response.StatusCode = 403;
            HttpContext.Current.Response.End();
        }

        CheckIpAddress(ip);
    }

    /// <summary>
    /// Checks the requesting IP address in the collection
    /// and bannes the IP if required.
    /// </summary>
    private static void CheckIpAddress(string ip)
    {
        lock (_IpAdresses)
        {
            if (!_IpAdresses.ContainsKey(ip))
            {
                _IpAdresses[ip] = 1;
            }
            else if (_IpAdresses[ip] == BANNED_REQUESTS)
            {
                //Баним по IP и юзеру в БД
                _Banned.Push(ip);
                _IpAdresses.Remove(ip);

                HttpApplication application = (HttpApplication)DosAttackModule.context;
                HttpContext context = application.Context;

                // Get the authentication cookie
                string cookieName = FormsAuthentication.FormsCookieName;
                HttpCookie authCookie = context.Request.Cookies[cookieName];
                //Если не аутентифицированный пользователь то ничего не делаем
                if (authCookie == null)
                    return;

                // Get the authentication ticket 
                // and rebuild the principal & identity
                FormsAuthenticationTicket authTicket =
                  FormsAuthentication.Decrypt(authCookie.Value);
                MembershipUser U=Membership.GetUser(authTicket.Name);
                U.IsApproved = false;
                Membership.UpdateUser(U);
                FormsAuthentication.SignOut();
                HttpContext.Current.Response.End();
                //context.Request.Cookies.Remove(cookieName);
                //Забанен совсем!!!
            }
            else
            {
                _IpAdresses[ip]++;
            }
        }
    }

    #region Timers

    /// <summary>
    /// Creates the timer that substract a request
    /// from the _IpAddress dictionary.
    /// </summary>
    private static Timer CreateTimer()
    {
        Timer timer = GetTimer(REDUCTION_INTERVAL);
        timer.Elapsed += new ElapsedEventHandler(TimerElapsed);
        return timer;
    }

    /// <summary>
    /// Creates the timer that removes 1 banned IP address
    /// everytime the timer is elapsed.
    /// </summary>
    /// <returns></returns>
    private static Timer CreateBanningTimer()
    {
        Timer timer = GetTimer(RELEASE_INTERVAL);
        timer.Elapsed += delegate { if (_Banned.Count!=0) _Banned.Pop(); };
        return timer;
    }

    /// <summary>
    /// Creates a simple timer instance and starts it.
    /// </summary>
    /// <param name="interval">The interval in milliseconds.</param>
    private static Timer GetTimer(int interval)
    {
        Timer timer = new Timer();
        timer.Interval = interval;
        timer.Start();

        return timer;
    }

    /// <summary>
    /// Substracts a request from each IP address in the collection.
    /// </summary>
    private static void TimerElapsed(object sender, ElapsedEventArgs e)
    {
        lock (_IpAdresses)
        {
            string[] tmpKeys = new string[_IpAdresses.Keys.Count];
            int i = 0;
            foreach (string key in _IpAdresses.Keys)
            {
                tmpKeys[i++] = key;
            }

            foreach (string key in tmpKeys)
            {
                _IpAdresses[key] -= BANNED_REQUESTS;
                if (_IpAdresses[key] <= 0)
                    _IpAdresses.Remove(key);
            }
        }
        //foreach (string key in _IpAdresses.Keys)
        //{
        //    _IpAdresses[key] -= BANNED_REQUESTS;
        //    if (_IpAdresses[key] <= 0)
        //        _IpAdresses.Remove(key);
        //}
    }

    #endregion
}